#include "util.h"


int getBroadcastAddress(char *interface, char *szBroadcast){

    int socketd;
    struct ifreq ifr;
    
    strcpy(ifr.ifr_name, interface);

    socketd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socketd <= 0){
        perror("socket");
        return 1;
    }

    if (ioctl(socketd, SIOCGIFBRDADDR, &ifr) == 0){ /* system call to get the address */
        strcpy(szBroadcast, inet_ntoa(((struct sockaddr_in *)&ifr.ifr_broadaddr)->sin_addr));
    }

    close(socketd);
    
    return 0;
}

int getIPAddress(char *interface, char *szIPAddr){

    int socketd;
    struct ifreq ifr;
    
    strcpy(ifr.ifr_name, interface);

    socketd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socketd <= 0){
        perror("socket");
        return 1;
    }

    if (ioctl(socketd, SIOCGIFADDR, &ifr) == 0){ /* system call to get the address */
        strcpy(szIPAddr, inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));
    }

    close(socketd);
    
    return 0;
}

void construct_address(struct sockaddr_in* broadcastAddr, char* address, unsigned short port){
    memset(broadcastAddr, 0, sizeof(*broadcastAddr));   	    /* Zero out structure */
    broadcastAddr->sin_family = AF_INET;                		/* Internet address family */
    broadcastAddr->sin_addr.s_addr = inet_addr(address);	    /* Broadcast IP address */
    broadcastAddr->sin_port = htons(port);         	            /* Broadcast port */
}

int send_broadcast_msg(char* msg, int sendMsgLen){

    int broadcastPermission;          /* Socket opt to set permission to broadcast */
	int sock;
    struct sockaddr_in broadcastAddr;

    /* Create socket for sending datagrams */
    if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
        perror("socket() failed");

    /* Set socket to allow broadcast */
    broadcastPermission = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (void *) &broadcastPermission, sizeof(broadcastPermission)) < 0)
        perror("setsockopt() failed");

    /* Construct local address structure */
    construct_address(&broadcastAddr, broadcastIP, broadcastPort);

    /* Send message */
    if (sendto(sock, msg, sendMsgLen, 0, (struct sockaddr *) &broadcastAddr, sizeof(broadcastAddr)) != sendMsgLen){
        perror("sendto() sent a different number of bytes than expected");
	    return 1;
    }

	close(sock);

	return sendMsgLen;
}

int send_unicast_msg(char* msg, int sendMsgLen, char* id_dest){

	int sock;
	struct sockaddr_in unicastAddr;

	/* Create socket to sending datagrams */
	if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
		perror("socket() failed");

	/* Construct local address structure */
    construct_address(&unicastAddr, id_dest, broadcastPort);

    /* Send message */
    if (sendto(sock, msg, sendMsgLen, 0, (struct sockaddr *) &unicastAddr, sizeof(unicastAddr)) != sendMsgLen){
        perror("sendto() sent a different number of bytes than expected");
	    return 1;
    }

	close(sock);

    return 0;
}

int recv_msg(){
   
	int sock;
    struct sockaddr_in broadcastAddr;

	printf("Binding with port %u\n", broadcastPort);

    /* Create a best-effort datagram socket using UDP */
    if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
        perror("socket() failed");

    /* Construct bind structure */
    memset(&broadcastAddr, 0, sizeof(broadcastAddr));   /* Zero out structure */
    broadcastAddr.sin_family = AF_INET;                 /* Internet address family */
    broadcastAddr.sin_addr.s_addr = htonl(INADDR_ANY);  /* Any incoming interface */
    broadcastAddr.sin_port = htons(broadcastPort);      /* Broadcast port */
    
     /* Bind to the broadcast port */
    if (bind(sock, (struct sockaddr *) &broadcastAddr, sizeof(broadcastAddr)) < 0)
        perror("bind() failed");

    char recvString[MAXRECVSTRING]; /* Buffer for received string */
    int recvStringLen = 0;          /* Length of received string */
    
    printf("probabilidade de erro: %f\n", prob_erro);
	printf("Receiving Started!\n");    


    while(1){

		strcpy(recvString, "");

        /* Receive a single datagram from the server */
        if ((recvStringLen = recvfrom(sock, recvString, MAXRECVSTRING, 0, NULL, 0)) < 0)
            perror("recvfrom() failed");
                        
            char* msg;
            char* block = recvString;
            
            /* desencapsulating the message */
            Header hdr;
            desencapsula(block, &msg, &hdr);
            
            /* injecting errors*/
            int erro = rand();
            if (erro <= RAND_MAX*(1 - prob_erro)){
            
                printf("Received: Message id %d:  %s de %s - type: %d\n", hdr.id, msg, hdr.orig_id, hdr.type);    /* Print the received string */
                LogFile ("Received: Message id %d:  %s de %s - type: %d\n", hdr.id, msg, hdr.orig_id, hdr.type);                
                
                /* Send acknowledge message */
                if (hdr.type == MSG){
                    if(strcmp(last_header.orig_id, "") == 0) strcpy(last_header.orig_id, hdr.orig_id);
				    
				    if (hdr.id > last_header.id){
					    //last_message_id = hdr.id;
					    isInOrder = insertInOrder (msg, &hdr, &received_messages, &last_header);
					    //LOG fprintf(LOGFile, "%d em order: %d\n", last_header.id, isInOrder);
					    //printf("%d em order: %d\n", last_header.id, isInOrder);
				    }
				    //if (isNack && hdr.id > (last_header.id + 1)){
				    if (isNack && !isInOrder){
					    send_nack();
				    }
                    if (isAck) send_ack(hdr.id, hdr.orig_id);
                } 
                 
			    /* If a NACK was received, the last message is sent again */
                if (hdr.type == NACK){                    
                    //if (strcmp(LastMessageinOrder, "")){
                    if(last_header.id > 0){
                    printf("Nack requesting msg id: %d, from %s\n", hdr.id + 1, hdr.dest_id);
                    LogFile ("Nack requesting msg id: %d, from %s\n", hdr.id + 1, hdr.dest_id);
                    	Message* msg = getMessage(hdr.id + 1, hdr.dest_id, &received_messages);
					    if (msg != NULL){
						    char* block;
						    Header auxhdr;
						    initialize_header (&auxhdr, msg->hdr.id, 'N', msg->hdr.type, msg->hdr.orig_id, msg->hdr.dest_id);
						    int len = encapsula(&block, msg->msg_text, &auxhdr);
						    printf("Sending requested message id: %d\n", hdr.id + 1);
						    send_broadcast_msg(block, len);
					    }
					    else{
					        printf("Nao encontrou\n");
					    }
				    }
                }
                if (hdr.type == ACK){
                    
                }
                
			    /* Reset the timer when the NACK mode is active */  
                if (isNack && hdr.type == MSG){
			        mysettimer(wait);
			        mysethandler();
			    }
			    if (hdr.type == CLOSE){
			        mysettimer(0);
			        printList (&received_messages);
			    }
			}
			
			else {
			    printf("ERRO DE ENVIO\n");
			}
			
    }
	close(sock);

}

void receive_ack(char IPAddress	[255], unsigned short int  msg_id){
    int index_host = -1;
    int i = 0;
    for (; i < MAXNUMHOSTS; i++){
        if (strcmp(ackHostsList[i], IPAddress) == 0){
            index_host = i;
            break;
        }
        if (strcmp(ackHostsList[i], "EMPTY") == 0){
            strcpy(ackHostsList[i], IPAddress);
            index_host = 1;
            int m = 0;
            for (; m < MAXNUMMSG; m++){
                ackControl[index_host][m] = 'N';
            }
            break;
        }
        ackControl[index_host][msg_id] = 'Y';
    }
}

void ackMessageControl(){
    int i = 0;
    for (; i < last_header.id; i++){
        int host = 0;
        while (strcmp(ackHostsList[i], "EMPTY")){
            if (ackControl[host][i] != 'y'){
                //envia mensagem
                break;
            }
        }
    }
}

void mysettimer(int milisegundos){

    struct itimerval newvalue, oldvalue;

    newvalue.it_value.tv_sec  = milisegundos / 1000;
    newvalue.it_value.tv_usec = milisegundos % 1000 * 1000;
    newvalue.it_interval.tv_sec  = 0;
    newvalue.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &newvalue, &oldvalue);
}

void mysethandler(){
    signal(SIGALRM,timer_handler);
}

void send_ack(int msg_id, char* id_dest){
	char msg[MAXRECVSTRING];
	sprintf(msg, "%d", msg_id);
	Header hdr;
	initialize_header (&hdr, -1, 'A', ACK, IPAddress, id_dest);
	
	char* block;
	int sendMsgLen = encapsula(&block, msg, &hdr);

	send_unicast_msg(block, sendMsgLen, id_dest);
}

void send_nack(){

	char msg_text [] = "NACK";
	//Message* msg = getLast(&received_messages);

	Header hdr;
	initialize_header (&hdr, last_header.id, 'N', NACK, IPAddress, last_header.orig_id);

	char* block;
	int sendMsgLen = encapsula(&block, msg_text, &hdr);

	send_broadcast_msg(block, sendMsgLen);
}

void timer_handler(int signum)
{
    send_nack();
    mysettimer(wait);
}

void LogFile(char* logInfo, ...){

    if (!isLogging) return;

    const char *p;
    va_list argp;
    int i;
    double d;
    char* s;
    char str[10];
    char logAux[256];
    char log[256];
    

    strcpy(logAux, logInfo);
    strcpy(log, "");
    
    
    va_start(argp, logInfo);

    //parsing log message    
    for(p = logAux; *p != '\0'; p++){
        strcpy(str, "");
	    if(*p != '%'){
            sprintf(str, "%c", *p);
    	    strncat(log, str, strlen(str));
		}
        else{
	        switch(*++p){
		        case 'c':
			        i = va_arg(argp, int);
			        sprintf(str, "%c", i);
			        strncat(log, str, strlen(str));
			        break;

		        case 'd':
			        i = va_arg(argp, int);
			        sprintf(str, "%d", i);
			        strncat(log, str, strlen(str));
			        break;

		        case 's':
			        s = va_arg(argp, char *);
			        strncat(log, s, strlen(s));
			        break;

		        case 'f':
			        d = va_arg(argp, double);
			        sprintf(str, "%f", d);
			        strncat(log, str, strlen(str));
			        break;

		        case '%':
			        putchar('%');
			        break;
	        }
	    }
	}

    va_end(argp);

    //printing log message in the log file
    FILE* LOGFile;
    LOGFile = fopen("relBroad.log", "a");
    
    if(LOGFile)
        fprintf(LOGFile, "%s\n", log);
        
    fclose(LOGFile);   
    
    
}

